/*
 * Ill_Be_BachView.java
 */

package ill_be_bach.view;

import ill_be_bach.Ill_Be_BachApp;
import ill_be_bach.MainController;
import ill_be_bach.view.KeyboardRenderer;
import ill_be_bach.view.PrintUtilities;
import ill_be_bach.view.SheetMusicRenderer;
import ill_be_bach.model.Beat;
import ill_be_bach.model.Accidental;
import ill_be_bach.model.Tempo;
import ill_be_bach.model.Key;
import ill_be_bach.model.KeySignature;
import ill_be_bach.model.Song;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.*;
import org.jdesktop.application.Action;
import org.jdesktop.application.ResourceMap;
import org.jdesktop.application.SingleFrameApplication;
import org.jdesktop.application.FrameView;
import org.jdesktop.application.TaskMonitor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.swing.DefaultComboBoxModel;
import javax.swing.Timer;
import javax.swing.Icon;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.filechooser.FileFilter;
import javax.swing.filechooser.FileNameExtensionFilter;

/**
 * The application's main frame.
 */
public class Ill_Be_BachView extends FrameView {

    public Ill_Be_BachView(SingleFrameApplication app) {
        super(app);

        initComponents();

        // status bar initialization - message timeout, idle icon and busy animation, etc
        ResourceMap resourceMap = getResourceMap();
        int messageTimeout = resourceMap.getInteger("StatusBar.messageTimeout");
        messageTimer = new Timer(messageTimeout, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                statusMessageLabel.setText("");
            }
        });
        messageTimer.setRepeats(false);
        int busyAnimationRate = resourceMap.getInteger("StatusBar.busyAnimationRate");
        for (int i = 0; i < busyIcons.length; i++) {
            busyIcons[i] = resourceMap.getIcon("StatusBar.busyIcons[" + i + "]");
        }
        busyIconTimer = new Timer(busyAnimationRate, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                busyIconIndex = (busyIconIndex + 1) % busyIcons.length;
                statusAnimationLabel.setIcon(busyIcons[busyIconIndex]);
            }
        });
        idleIcon = resourceMap.getIcon("StatusBar.idleIcon");
        statusAnimationLabel.setIcon(idleIcon);
        progressBar.setVisible(false);

        // connecting action tasks to status bar via TaskMonitor
        TaskMonitor taskMonitor = new TaskMonitor(getApplication().getContext());
        taskMonitor.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
            public void propertyChange(java.beans.PropertyChangeEvent evt) {
                String propertyName = evt.getPropertyName();
                if ("started".equals(propertyName)) {
                    if (!busyIconTimer.isRunning()) {
                        statusAnimationLabel.setIcon(busyIcons[0]);
                        busyIconIndex = 0;
                        busyIconTimer.start();
                    }
                    progressBar.setVisible(true);
                    progressBar.setIndeterminate(true);
                } else if ("done".equals(propertyName)) {
                    busyIconTimer.stop();
                    statusAnimationLabel.setIcon(idleIcon);
                    progressBar.setVisible(false);
                    progressBar.setValue(0);
                } else if ("message".equals(propertyName)) {
                    String text = (String)(evt.getNewValue());
                    statusMessageLabel.setText((text == null) ? "" : text);
                    messageTimer.restart();
                } else if ("progress".equals(propertyName)) {
                    int value = (Integer)(evt.getNewValue());
                    progressBar.setVisible(true);
                    progressBar.setIndeterminate(false);
                    progressBar.setValue(value);
                }
            }
        });

        setupForm() ;
    }

    @Action
    public void showAboutBox() {
        if (aboutBox == null) {
            JFrame mainFrame = Ill_Be_BachApp.getApplication().getMainFrame();
            aboutBox = new Ill_Be_BachAboutBox(mainFrame);
            aboutBox.setLocationRelativeTo(mainFrame);
        }
        Ill_Be_BachApp.getApplication().show(aboutBox);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        mainPanel = new javax.swing.JPanel();
        pnlSongProperties = new javax.swing.JPanel();
        jLabel7 = new javax.swing.JLabel();
        jLabel6 = new javax.swing.JLabel();
        txtTitle = new javax.swing.JTextField();
        btnSetSongProperties = new javax.swing.JButton();
        ddlTempo = new javax.swing.JComboBox();
        lblBeatsPerMinute = new javax.swing.JLabel();
        jScrollPane1 = new javax.swing.JScrollPane();
        lbxBassVoices = new javax.swing.JList();
        jLabel5 = new javax.swing.JLabel();
        jLabel8 = new javax.swing.JLabel();
        jScrollPane2 = new javax.swing.JScrollPane();
        lbxTrebleVoices = new javax.swing.JList();
        btnTrebleToBass = new javax.swing.JButton();
        btnBassToTreble = new javax.swing.JButton();
        ddlKeySignature = new javax.swing.JComboBox();
        jLabel9 = new javax.swing.JLabel();
        pnlKeyboard = new javax.swing.JPanel();
        btnRest = new javax.swing.JButton();
        btnChord = new javax.swing.JToggleButton();
        jLabel4 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        ddlVoice = new javax.swing.JComboBox();
        btnRemove = new javax.swing.JButton();
        pnlKeys = new javax.swing.JPanel() {
            public void paintComponent(Graphics g) {
                super.paintComponent(g);
                renderKeyboard(g, -1, false);
            }
        };
        sliderBeat = new javax.swing.JSlider();
        jLabel1 = new javax.swing.JLabel();
        jPanel2 = new javax.swing.JPanel();
        btnRecord = new javax.swing.JButton();
        btnClear = new javax.swing.JButton();
        btnSave = new javax.swing.JButton();
        btnPlay = new javax.swing.JButton();
        btnLoad = new javax.swing.JButton();
        btnPrint = new javax.swing.JButton();
        btnForward = new javax.swing.JButton();
        btnRewind = new javax.swing.JButton();
        pnlSheetMusicScrollPane = new javax.swing.JScrollPane();
        pnlSheetMusic = new javax.swing.JPanel() {
            public void paintComponent(Graphics g) {
                super.paintComponent(g);
                sheetMusicRenderer.draw(g, pnlSheetMusic, controller.getSong());
            }
        }
        ;
        btnStopPlay = new javax.swing.JButton();
        btnPause = new javax.swing.JButton();
        btnStopRecord = new javax.swing.JButton();
        btnEnableSheetMusic = new javax.swing.JToggleButton();
        menuBar = new javax.swing.JMenuBar();
        javax.swing.JMenu fileMenu = new javax.swing.JMenu();
        loadMenuItem = new javax.swing.JMenuItem();
        saveMenuItem = new javax.swing.JMenuItem();
        jMenuItem6 = new javax.swing.JMenuItem();
        printMenuItem = new javax.swing.JMenuItem();
        javax.swing.JMenuItem exitMenuItem = new javax.swing.JMenuItem();
        songMenu = new javax.swing.JMenu();
        jMenuItem3 = new javax.swing.JMenuItem();
        jMenuItem1 = new javax.swing.JMenuItem();
        jMenuItem2 = new javax.swing.JMenuItem();
        jMenuItem4 = new javax.swing.JMenuItem();
        jMenuItem5 = new javax.swing.JMenuItem();
        javax.swing.JMenu helpMenu = new javax.swing.JMenu();
        javax.swing.JMenuItem aboutMenuItem = new javax.swing.JMenuItem();
        statusPanel = new javax.swing.JPanel();
        javax.swing.JSeparator statusPanelSeparator = new javax.swing.JSeparator();
        statusMessageLabel = new javax.swing.JLabel();
        statusAnimationLabel = new javax.swing.JLabel();
        progressBar = new javax.swing.JProgressBar();
        lblError = new javax.swing.JLabel();

        mainPanel.setName("mainPanel"); // NOI18N

        pnlSongProperties.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        pnlSongProperties.setName("pnlSongProperties"); // NOI18N
        pnlSongProperties.setLayout(new org.netbeans.lib.awtextra.AbsoluteLayout());

        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(ill_be_bach.Ill_Be_BachApp.class).getContext().getResourceMap(Ill_Be_BachView.class);
        jLabel7.setText(resourceMap.getString("jLabel7.text")); // NOI18N
        jLabel7.setName("jLabel7"); // NOI18N
        pnlSongProperties.add(jLabel7, new org.netbeans.lib.awtextra.AbsoluteConstraints(310, 40, -1, -1));

        jLabel6.setText(resourceMap.getString("jLabel6.text")); // NOI18N
        jLabel6.setName("jLabel6"); // NOI18N
        pnlSongProperties.add(jLabel6, new org.netbeans.lib.awtextra.AbsoluteConstraints(10, 10, -1, -1));

        txtTitle.setText(resourceMap.getString("txtTitle.text")); // NOI18N
        txtTitle.setName("txtTitle"); // NOI18N
        pnlSongProperties.add(txtTitle, new org.netbeans.lib.awtextra.AbsoluteConstraints(90, 10, 160, -1));

        btnSetSongProperties.setText(resourceMap.getString("btnSetSongProperties.text")); // NOI18N
        btnSetSongProperties.setName("btnSetSongProperties"); // NOI18N
        btnSetSongProperties.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnSetSongPropertiesMousePressed(evt);
            }
        });
        pnlSongProperties.add(btnSetSongProperties, new org.netbeans.lib.awtextra.AbsoluteConstraints(10, 90, -1, -1));

        ddlTempo.setSelectedItem("Allegro");
        ddlTempo.setName("ddlTempo"); // NOI18N
        ddlTempo.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                ddlTempoActionPerformed(evt);
            }
        });
        pnlSongProperties.add(ddlTempo, new org.netbeans.lib.awtextra.AbsoluteConstraints(400, 40, 90, -1));

        lblBeatsPerMinute.setText(resourceMap.getString("lblBeatsPerMinute.text")); // NOI18N
        lblBeatsPerMinute.setName("lblBeatsPerMinute"); // NOI18N
        pnlSongProperties.add(lblBeatsPerMinute, new org.netbeans.lib.awtextra.AbsoluteConstraints(500, 40, -1, -1));

        jScrollPane1.setName("jScrollPane1"); // NOI18N

        lbxBassVoices.setName("lbxBassVoices"); // NOI18N
        jScrollPane1.setViewportView(lbxBassVoices);

        pnlSongProperties.add(jScrollPane1, new org.netbeans.lib.awtextra.AbsoluteConstraints(890, 20, 50, 90));

        jLabel5.setText(resourceMap.getString("jLabel5.text")); // NOI18N
        jLabel5.setName("jLabel5"); // NOI18N
        pnlSongProperties.add(jLabel5, new org.netbeans.lib.awtextra.AbsoluteConstraints(780, 0, -1, -1));

        jLabel8.setText(resourceMap.getString("jLabel8.text")); // NOI18N
        jLabel8.setName("jLabel8"); // NOI18N
        pnlSongProperties.add(jLabel8, new org.netbeans.lib.awtextra.AbsoluteConstraints(890, 0, -1, -1));

        jScrollPane2.setName("jScrollPane2"); // NOI18N

        lbxTrebleVoices.setName("lbxTrebleVoices"); // NOI18N
        jScrollPane2.setViewportView(lbxTrebleVoices);

        pnlSongProperties.add(jScrollPane2, new org.netbeans.lib.awtextra.AbsoluteConstraints(780, 20, 50, 90));

        btnTrebleToBass.setText(resourceMap.getString("btnTrebleToBass.text")); // NOI18N
        btnTrebleToBass.setName("btnTrebleToBass"); // NOI18N
        btnTrebleToBass.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnTrebleToBassMousePressed(evt);
            }
        });
        pnlSongProperties.add(btnTrebleToBass, new org.netbeans.lib.awtextra.AbsoluteConstraints(840, 30, 40, -1));

        btnBassToTreble.setText(resourceMap.getString("btnBassToTreble.text")); // NOI18N
        btnBassToTreble.setName("btnBassToTreble"); // NOI18N
        btnBassToTreble.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnBassToTrebleMousePressed(evt);
            }
        });
        pnlSongProperties.add(btnBassToTreble, new org.netbeans.lib.awtextra.AbsoluteConstraints(840, 70, 40, -1));

        ddlKeySignature.setName("ddlKeySignature"); // NOI18N
        pnlSongProperties.add(ddlKeySignature, new org.netbeans.lib.awtextra.AbsoluteConstraints(400, 10, 170, -1));

        jLabel9.setText(resourceMap.getString("jLabel9.text")); // NOI18N
        jLabel9.setName("jLabel9"); // NOI18N
        pnlSongProperties.add(jLabel9, new org.netbeans.lib.awtextra.AbsoluteConstraints(310, 10, -1, -1));

        pnlKeyboard.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        pnlKeyboard.setName("pnlKeyboard"); // NOI18N

        btnRest.setText(resourceMap.getString("btnRest.text")); // NOI18N
        btnRest.setName("btnRest"); // NOI18N
        btnRest.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnRestMousePressed(evt);
            }
        });

        btnChord.setText(resourceMap.getString("btnChord.text")); // NOI18N
        btnChord.setName("btnChord"); // NOI18N
        btnChord.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                btnChordMouseClicked(evt);
            }
        });

        jLabel4.setText(resourceMap.getString("jLabel4.text")); // NOI18N
        jLabel4.setName("jLabel4"); // NOI18N

        jLabel2.setText(resourceMap.getString("jLabel2.text")); // NOI18N
        jLabel2.setName("jLabel2"); // NOI18N

        ddlVoice.setModel(new javax.swing.DefaultComboBoxModel(new String[] { "0", "1" }));
        ddlVoice.setName("ddlVoice"); // NOI18N

        btnRemove.setText(resourceMap.getString("btnRemove.text")); // NOI18N
        btnRemove.setName("btnRemove"); // NOI18N
        btnRemove.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnRemoveMousePressed(evt);
            }
        });

        pnlKeys.setName("pnlKeys"); // NOI18N
        pnlKeys.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                pnlKeysMousePressed(evt);
            }
            public void mouseReleased(java.awt.event.MouseEvent evt) {
                pnlKeysMouseReleased(evt);
            }
        });

        javax.swing.GroupLayout pnlKeysLayout = new javax.swing.GroupLayout(pnlKeys);
        pnlKeys.setLayout(pnlKeysLayout);
        pnlKeysLayout.setHorizontalGroup(
            pnlKeysLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 1064, Short.MAX_VALUE)
        );
        pnlKeysLayout.setVerticalGroup(
            pnlKeysLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 100, Short.MAX_VALUE)
        );

        sliderBeat.setMaximum(3);
        sliderBeat.setPaintLabels(true);
        sliderBeat.setSnapToTicks(true);
        sliderBeat.setValue(0);
        sliderBeat.setName("sliderBeat"); // NOI18N

        jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N
        jLabel1.setName("jLabel1"); // NOI18N

        javax.swing.GroupLayout pnlKeyboardLayout = new javax.swing.GroupLayout(pnlKeyboard);
        pnlKeyboard.setLayout(pnlKeyboardLayout);
        pnlKeyboardLayout.setHorizontalGroup(
            pnlKeyboardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlKeyboardLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(pnlKeyboardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(pnlKeyboardLayout.createSequentialGroup()
                        .addComponent(jLabel1)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(sliderBeat, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(35, 35, 35)
                        .addComponent(btnRest)
                        .addGap(62, 62, 62)
                        .addComponent(jLabel2)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(ddlVoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(72, 72, 72)
                        .addComponent(jLabel4)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnChord)
                        .addGap(51, 51, 51)
                        .addComponent(btnRemove))
                    .addComponent(pnlKeys, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap(21, Short.MAX_VALUE))
        );
        pnlKeyboardLayout.setVerticalGroup(
            pnlKeyboardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(pnlKeyboardLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(pnlKeys, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addGroup(pnlKeyboardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(pnlKeyboardLayout.createSequentialGroup()
                        .addGap(18, 18, 18)
                        .addGroup(pnlKeyboardLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(jLabel1)
                            .addComponent(btnRest)
                            .addComponent(jLabel2)
                            .addComponent(ddlVoice, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addComponent(jLabel4)
                            .addComponent(btnChord)
                            .addComponent(btnRemove)))
                    .addGroup(pnlKeyboardLayout.createSequentialGroup()
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(sliderBeat, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
                .addGap(11, 11, 11))
        );

        jPanel2.setBorder(javax.swing.BorderFactory.createEtchedBorder());
        jPanel2.setName("jPanel2"); // NOI18N

        btnRecord.setText(resourceMap.getString("btnRecord.text")); // NOI18N
        btnRecord.setName("btnRecord"); // NOI18N
        btnRecord.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnRecordMousePressed(evt);
            }
        });

        btnClear.setText(resourceMap.getString("btnClear.text")); // NOI18N
        btnClear.setName("btnClear"); // NOI18N
        btnClear.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnClearMousePressed(evt);
            }
        });

        btnSave.setText(resourceMap.getString("btnSave.text")); // NOI18N
        btnSave.setName("btnSave"); // NOI18N
        btnSave.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnSaveMousePressed(evt);
            }
        });

        btnPlay.setText(resourceMap.getString("btnPlay.text")); // NOI18N
        btnPlay.setName("btnPlay"); // NOI18N
        btnPlay.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnPlayMousePressed(evt);
            }
        });

        btnLoad.setText(resourceMap.getString("btnLoad.text")); // NOI18N
        btnLoad.setName("btnLoad"); // NOI18N
        btnLoad.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnLoadMousePressed(evt);
            }
        });

        btnPrint.setText(resourceMap.getString("btnPrint.text")); // NOI18N
        btnPrint.setName("btnPrint"); // NOI18N
        btnPrint.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnPrintMousePressed(evt);
            }
        });

        btnForward.setText(resourceMap.getString("btnForward.text")); // NOI18N
        btnForward.setName("btnForward"); // NOI18N
        btnForward.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnForwardMousePressed(evt);
            }
        });

        btnRewind.setText(resourceMap.getString("btnRewind.text")); // NOI18N
        btnRewind.setName("btnRewind"); // NOI18N
        btnRewind.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnRewindMousePressed(evt);
            }
        });

        pnlSheetMusicScrollPane.setVerticalScrollBarPolicy(javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
        pnlSheetMusicScrollPane.setAutoscrolls(true);
        pnlSheetMusicScrollPane.setName("pnlSheetMusicScrollPane"); // NOI18N

        pnlSheetMusic.setAutoscrolls(true);
        pnlSheetMusic.setName("pnlSheetMusic"); // NOI18N
        pnlSheetMusic.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                pnlSheetMusicMousePressed(evt);
            }
        });

        javax.swing.GroupLayout pnlSheetMusicLayout = new javax.swing.GroupLayout(pnlSheetMusic);
        pnlSheetMusic.setLayout(pnlSheetMusicLayout);
        pnlSheetMusicLayout.setHorizontalGroup(
            pnlSheetMusicLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 1322, Short.MAX_VALUE)
        );
        pnlSheetMusicLayout.setVerticalGroup(
            pnlSheetMusicLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 298, Short.MAX_VALUE)
        );

        pnlSheetMusicScrollPane.setViewportView(pnlSheetMusic);

        btnStopPlay.setText(resourceMap.getString("btnStopPlay.text")); // NOI18N
        btnStopPlay.setName("btnStopPlay"); // NOI18N
        btnStopPlay.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnStopPlayMousePressed(evt);
            }
        });

        btnPause.setText(resourceMap.getString("btnPause.text")); // NOI18N
        btnPause.setName("btnPause"); // NOI18N
        btnPause.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnPauseMousePressed(evt);
            }
        });

        btnStopRecord.setText(resourceMap.getString("btnStopRecord.text")); // NOI18N
        btnStopRecord.setName("btnStopRecord"); // NOI18N
        btnStopRecord.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                btnStopRecordMousePressed(evt);
            }
        });

        btnEnableSheetMusic.setText(resourceMap.getString("btnEnableSheetMusic.text")); // NOI18N
        btnEnableSheetMusic.setName("btnEnableSheetMusic"); // NOI18N
        btnEnableSheetMusic.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                btnEnableSheetMusicMouseClicked(evt);
            }
        });

        javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
        jPanel2.setLayout(jPanel2Layout);
        jPanel2Layout.setHorizontalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addComponent(btnRecord)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnStopRecord)
                        .addGap(34, 34, 34)
                        .addComponent(btnPlay)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnPause)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnStopPlay)
                        .addGap(38, 38, 38)
                        .addComponent(btnRewind)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnForward)
                        .addGap(43, 43, 43)
                        .addComponent(btnSave)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnLoad)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnPrint)
                        .addGap(47, 47, 47)
                        .addComponent(btnClear)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(btnEnableSheetMusic))
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addGap(10, 10, 10)
                        .addComponent(pnlSheetMusicScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 1075, Short.MAX_VALUE)))
                .addContainerGap())
        );
        jPanel2Layout.setVerticalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(btnRecord)
                    .addComponent(btnSave)
                    .addComponent(btnLoad)
                    .addComponent(btnPrint)
                    .addComponent(btnClear)
                    .addComponent(btnStopRecord)
                    .addComponent(btnRewind)
                    .addComponent(btnForward)
                    .addComponent(btnStopPlay)
                    .addComponent(btnPause)
                    .addComponent(btnPlay)
                    .addComponent(btnEnableSheetMusic))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(pnlSheetMusicScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 311, Short.MAX_VALUE)
                .addContainerGap())
        );

        javax.swing.GroupLayout mainPanelLayout = new javax.swing.GroupLayout(mainPanel);
        mainPanel.setLayout(mainPanelLayout);
        mainPanelLayout.setHorizontalGroup(
            mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(mainPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                    .addComponent(pnlSongProperties, javax.swing.GroupLayout.DEFAULT_SIZE, 1099, Short.MAX_VALUE)
                    .addComponent(pnlKeyboard, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        mainPanelLayout.setVerticalGroup(
            mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(mainPanelLayout.createSequentialGroup()
                .addComponent(pnlKeyboard, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(pnlSongProperties, javax.swing.GroupLayout.PREFERRED_SIZE, 125, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        menuBar.setName("menuBar"); // NOI18N

        fileMenu.setText(resourceMap.getString("fileMenu.text")); // NOI18N
        fileMenu.setName("fileMenu"); // NOI18N

        javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(ill_be_bach.Ill_Be_BachApp.class).getContext().getActionMap(Ill_Be_BachView.class, this);
        loadMenuItem.setAction(actionMap.get("load")); // NOI18N
        loadMenuItem.setText(resourceMap.getString("loadMenuItem.text")); // NOI18N
        loadMenuItem.setName("loadMenuItem"); // NOI18N
        fileMenu.add(loadMenuItem);

        saveMenuItem.setAction(actionMap.get("save")); // NOI18N
        saveMenuItem.setText(resourceMap.getString("saveMenuItem.text")); // NOI18N
        saveMenuItem.setName("saveMenuItem"); // NOI18N
        fileMenu.add(saveMenuItem);

        jMenuItem6.setAction(actionMap.get("saveAsImage")); // NOI18N
        jMenuItem6.setText(resourceMap.getString("jMenuItem6.text")); // NOI18N
        jMenuItem6.setName("jMenuItem6"); // NOI18N
        fileMenu.add(jMenuItem6);

        printMenuItem.setAction(actionMap.get("print")); // NOI18N
        printMenuItem.setText(resourceMap.getString("printMenuItem.text")); // NOI18N
        printMenuItem.setName("printMenuItem"); // NOI18N
        fileMenu.add(printMenuItem);

        exitMenuItem.setAction(actionMap.get("quit")); // NOI18N
        exitMenuItem.setName("exitMenuItem"); // NOI18N
        fileMenu.add(exitMenuItem);

        menuBar.add(fileMenu);

        songMenu.setText(resourceMap.getString("songMenu.text")); // NOI18N
        songMenu.setName("songMenu"); // NOI18N

        jMenuItem3.setAction(actionMap.get("preview")); // NOI18N
        jMenuItem3.setText(resourceMap.getString("jMenuItem3.text")); // NOI18N
        jMenuItem3.setName("jMenuItem3"); // NOI18N
        songMenu.add(jMenuItem3);

        jMenuItem1.setAction(actionMap.get("rewind")); // NOI18N
        jMenuItem1.setText(resourceMap.getString("jMenuItem1.text")); // NOI18N
        jMenuItem1.setName("jMenuItem1"); // NOI18N
        songMenu.add(jMenuItem1);

        jMenuItem2.setAction(actionMap.get("forward")); // NOI18N
        jMenuItem2.setText(resourceMap.getString("jMenuItem2.text")); // NOI18N
        jMenuItem2.setName("jMenuItem2"); // NOI18N
        songMenu.add(jMenuItem2);

        jMenuItem4.setAction(actionMap.get("clear")); // NOI18N
        jMenuItem4.setText(resourceMap.getString("jMenuItem4.text")); // NOI18N
        jMenuItem4.setName("jMenuItem4"); // NOI18N
        songMenu.add(jMenuItem4);

        jMenuItem5.setAction(actionMap.get("record")); // NOI18N
        jMenuItem5.setText(resourceMap.getString("jMenuItem5.text")); // NOI18N
        jMenuItem5.setName("jMenuItem5"); // NOI18N
        songMenu.add(jMenuItem5);

        menuBar.add(songMenu);

        helpMenu.setText(resourceMap.getString("helpMenu.text")); // NOI18N
        helpMenu.setName("helpMenu"); // NOI18N

        aboutMenuItem.setAction(actionMap.get("showAboutBox")); // NOI18N
        aboutMenuItem.setName("aboutMenuItem"); // NOI18N
        helpMenu.add(aboutMenuItem);

        menuBar.add(helpMenu);

        statusPanel.setName("statusPanel"); // NOI18N

        statusPanelSeparator.setName("statusPanelSeparator"); // NOI18N

        statusMessageLabel.setName("statusMessageLabel"); // NOI18N

        statusAnimationLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
        statusAnimationLabel.setName("statusAnimationLabel"); // NOI18N

        progressBar.setName("progressBar"); // NOI18N

        lblError.setForeground(resourceMap.getColor("lblError.foreground")); // NOI18N
        lblError.setText(resourceMap.getString("lblError.text")); // NOI18N
        lblError.setName("lblError"); // NOI18N

        javax.swing.GroupLayout statusPanelLayout = new javax.swing.GroupLayout(statusPanel);
        statusPanel.setLayout(statusPanelLayout);
        statusPanelLayout.setHorizontalGroup(
            statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(statusPanelSeparator, javax.swing.GroupLayout.DEFAULT_SIZE, 1119, Short.MAX_VALUE)
            .addGroup(statusPanelLayout.createSequentialGroup()
                .addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addGroup(statusPanelLayout.createSequentialGroup()
                        .addContainerGap()
                        .addComponent(statusMessageLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 945, Short.MAX_VALUE))
                    .addGroup(statusPanelLayout.createSequentialGroup()
                        .addContainerGap()
                        .addComponent(lblError, javax.swing.GroupLayout.PREFERRED_SIZE, 340, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)))
                .addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addGroup(statusPanelLayout.createSequentialGroup()
                        .addGap(154, 154, 154)
                        .addComponent(statusAnimationLabel)
                        .addContainerGap())
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, statusPanelLayout.createSequentialGroup()
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                        .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, 77, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addGap(25, 25, 25))))
        );
        statusPanelLayout.setVerticalGroup(
            statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(statusPanelLayout.createSequentialGroup()
                .addComponent(statusPanelSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(statusPanelLayout.createSequentialGroup()
                        .addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                            .addComponent(statusMessageLabel)
                            .addComponent(statusAnimationLabel))
                        .addGap(0, 0, 0)
                        .addComponent(lblError, javax.swing.GroupLayout.PREFERRED_SIZE, 9, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addContainerGap())
        );

        setComponent(mainPanel);
        setMenuBar(menuBar);
        setStatusBar(statusPanel);
    }// </editor-fold>//GEN-END:initComponents

    private MainController controller = new MainController() ;
    private SheetMusicRenderer sheetMusicRenderer = new SheetMusicRenderer() ;
    private KeyboardRenderer keyboardRenderer = new KeyboardRenderer() ;
    private JFileChooser songFileChooser = new JFileChooser() ;
    private JFileChooser imageFileChooser = new JFileChooser() ;
    
    private Map<String, Beat> beatMap = new LinkedHashMap<String, Beat>() ;
    private Map<String, Tempo> tempoMap = new LinkedHashMap<String, Tempo>();
    private Map<String, KeySignature> keySignatureMap = new LinkedHashMap<String, KeySignature>() ;

    ArrayList<Integer> trebleVoicesInListbox ;
    ArrayList<Integer> bassVoicesInListbox ;

    private void setupForm() {
        // map UI strings/values to enumerations
        beatMap.put("8", Beat.EIGHTH);
        beatMap.put("4", Beat.QUARTER);
        beatMap.put("2", Beat.HALF);
        beatMap.put("1", Beat.WHOLE);

        tempoMap.put("Grave", Tempo.Grave);
        tempoMap.put("Largo", Tempo.Largo);
        tempoMap.put("Larghetto", Tempo.Larghetto);
        tempoMap.put("Lento", Tempo.Lento);
        tempoMap.put("Adagio", Tempo.Adagio);
        tempoMap.put("Adagietto", Tempo.Adagietto);
        tempoMap.put("Andante", Tempo.Andante);
        tempoMap.put("Andantino", Tempo.Andantino);
        tempoMap.put("Moderato", Tempo.Moderato);
        tempoMap.put("Allegreto", Tempo.Allegreto);
        tempoMap.put("Allegro", Tempo.Allegro);
        tempoMap.put("Vivace", Tempo.Vivace);
        tempoMap.put("Presto", Tempo.Presto);
        tempoMap.put("Prestissimo", Tempo.Prestissimo);

        keySignatureMap.put("C major / A minor", KeySignature.CMajor_AMinor);
        keySignatureMap.put("G major/ E minor", KeySignature.GMajor_EMinor);
        keySignatureMap.put("D major / B minor", KeySignature.DMajor_BMinor);
        keySignatureMap.put("A Major / F# Minor", KeySignature.AMajor_FSharpMinor);
        keySignatureMap.put("E Major / C# Minor", KeySignature.EMajor_CSharpMinor);
        keySignatureMap.put("B Major / G# Minor", KeySignature.BMajor_GSharpMinor);
        keySignatureMap.put("F# Major / D# Minor", KeySignature.FSharpMajor_DSharpMinor);
        keySignatureMap.put("Gb Major / Eb Minor", KeySignature.GFlatMajor_EFlatMinor);
        keySignatureMap.put("Db Major / Bb Minor", KeySignature.DFlatMajor_BFlatMinor);
        keySignatureMap.put("Ab Major / F Minor", KeySignature.AFlatMajor_FMinor);
        keySignatureMap.put("Eb Major / C Minor", KeySignature.EFlatMajor_CMinor);
        keySignatureMap.put("Bb Major / G Minor", KeySignature.BFlatMajor_GMinor);
        keySignatureMap.put("F Major / D Minor", KeySignature.FMajor_DMinor);

        // initialize UI to default values
        this.ddlKeySignature.setModel(new DefaultComboBoxModel(keySignatureMap.keySet().toArray()));
        this.ddlTempo.setModel(new DefaultComboBoxModel(tempoMap.keySet().toArray()));
        ddlTempo.setSelectedItem("Allegro");
        this.displayBeatsPerMinute();

        sliderBeat.setMaximum(beatMap.size() - 1);
        Iterator<String> iter = beatMap.keySet().iterator();
        Hashtable<Integer, JLabel> labels = new Hashtable<Integer, JLabel>();
        for (int i = 0 ; i < beatMap.size() ; i++)
            labels.put(i, new JLabel(iter.next()));
        sliderBeat.setLabelTable(labels);

        trebleVoicesInListbox = new ArrayList<Integer>();
        bassVoicesInListbox = new ArrayList<Integer>();
        Integer[] allVoices = new Integer[Song.getMaxVoices()];
        for (int i = 0 ; i < Song.getMaxVoices() ; i++) {
            if (i % 2 == 0)
                trebleVoicesInListbox.add(i);
            else
                bassVoicesInListbox.add(i);
            allVoices[i] = i ;
        }
        ddlVoice.setModel(new DefaultComboBoxModel(allVoices));
        lbxTrebleVoices.setListData(trebleVoicesInListbox.toArray());
        lbxBassVoices.setListData(bassVoicesInListbox.toArray());

        this.txtTitle.setText("insert song title") ;

        this.btnEnableSheetMusic.setSelected(true);

        // initialize song with default values
        setSongProperties();

        // determine song filters
        songFileChooser.addChoosableFileFilter(new FileFilter() {
            public boolean accept(File f) { return f.getName().endsWith(".midi"); }
            public String getDescription() { return "Midi (*.midi)"; }
        });
        songFileChooser.addChoosableFileFilter(new FileFilter() {
            public boolean accept(File f) { return f.getName().endsWith(".illbebach"); }
            public String getDescription() { return "I'll Be Bach Sheet (*.illbebach)"; }
        });
        songFileChooser.removeChoosableFileFilter(songFileChooser.getAcceptAllFileFilter());
        imageFileChooser.addChoosableFileFilter(new FileFilter() {
            public boolean accept(File f) {
                return f.getName().matches("([^\\s]+(\\.(?i)(jpg|png|gif|bmp))$)");
            }
            public String getDescription() { return "Image (*.jpg, *.png, *.gif, *.bmp)"; }
        });
        imageFileChooser.removeChoosableFileFilter(imageFileChooser.getAcceptAllFileFilter());
    }

    private void playNote(Key key, Accidental accidental, int octave) {
        int selectedBeat = sliderBeat.getValue();
        Beat beat = beatMap.get(beatMap.keySet().toArray()[selectedBeat]);
        if (beat == null) {
            showErrorInDialog("Unexpected error recognizing beat!");
            return ;
        }
        controller.play(new Integer(ddlVoice.getSelectedItem().toString()), btnChord.isSelected(), key, accidental, beat, octave, this.btnEnableSheetMusic.isSelected());
        renderSheetMusic();
    }

    private void displayBeatsPerMinute() {
        String selectedTemp = ddlTempo.getSelectedItem().toString();
        Tempo tempo = tempoMap.get(selectedTemp);
        if (tempo == null) {
            showErrorInDialog("Unexpected error recognizing tempo!");
            return ;
        }
        this.lblBeatsPerMinute.setText(tempo.getBeatsPerMinute() + " beats per minute");
    }

    private void setSongProperties() {
        controller.setSongProperties(txtTitle.getText().trim(),
                tempoMap.get(ddlTempo.getSelectedItem()),
                trebleVoicesInListbox,
                bassVoicesInListbox,
                keySignatureMap.get(ddlKeySignature.getSelectedItem()));
    }
    
    private void btnChordMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnChordMouseClicked
        if (btnChord.isSelected()) {
            btnChord.setText("On");
        }
        else
            btnChord.setText("Off");
    }//GEN-LAST:event_btnChordMouseClicked

    private void btnPlayMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnPlayMousePressed
        play() ;
    }//GEN-LAST:event_btnPlayMousePressed

    private void btnClearMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnClearMousePressed
        if (JOptionPane.showConfirmDialog(null, "Are you sure you want to clear the song contents?",
                "Clear", JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) {
            clear() ;
        }
    }//GEN-LAST:event_btnClearMousePressed

    private void btnRestMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnRestMousePressed
        playNote(Key.Rest, Accidental.None, 0);
    }//GEN-LAST:event_btnRestMousePressed

    private void btnSaveMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnSaveMousePressed
        save() ;
    }//GEN-LAST:event_btnSaveMousePressed

    private void btnLoadMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnLoadMousePressed
        load() ;
    }//GEN-LAST:event_btnLoadMousePressed

    private void btnPrintMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnPrintMousePressed
        print() ;
    }//GEN-LAST:event_btnPrintMousePressed

    private void btnRewindMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnRewindMousePressed
        rewind() ;
    }//GEN-LAST:event_btnRewindMousePressed

    private void btnForwardMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnForwardMousePressed
        forward() ;
    }//GEN-LAST:event_btnForwardMousePressed

    private void btnSetSongPropertiesMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnSetSongPropertiesMousePressed
        setSongProperties() ;
        renderSheetMusic();
    }//GEN-LAST:event_btnSetSongPropertiesMousePressed

    private void ddlTempoActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_ddlTempoActionPerformed
        this.displayBeatsPerMinute();
    }//GEN-LAST:event_ddlTempoActionPerformed

    private void btnTrebleToBassMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnTrebleToBassMousePressed
        for (Object selectedVoice : lbxTrebleVoices.getSelectedValues()) {
            this.trebleVoicesInListbox.remove(new Integer(selectedVoice.toString()));
            this.bassVoicesInListbox.add(new Integer(selectedVoice.toString()));
        }
        lbxTrebleVoices.setListData(sort(trebleVoicesInListbox).toArray());
        lbxBassVoices.setListData(sort(bassVoicesInListbox).toArray());
    }//GEN-LAST:event_btnTrebleToBassMousePressed

    private void btnBassToTrebleMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnBassToTrebleMousePressed
        for (Object selectedVoice : lbxBassVoices.getSelectedValues()) {
            this.trebleVoicesInListbox.add(new Integer(selectedVoice.toString()));
            this.bassVoicesInListbox.remove(new Integer(selectedVoice.toString()));
        }
        lbxTrebleVoices.setListData(sort(trebleVoicesInListbox).toArray());
        lbxBassVoices.setListData(sort(bassVoicesInListbox).toArray());
    }//GEN-LAST:event_btnBassToTrebleMousePressed

    private void btnRemoveMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnRemoveMousePressed
        controller.remove(new Integer(ddlVoice.getSelectedItem().toString()));
        renderSheetMusic();
    }//GEN-LAST:event_btnRemoveMousePressed

    private void btnRecordMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnRecordMousePressed
        record() ;
    }//GEN-LAST:event_btnRecordMousePressed

    private void pnlKeysMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_pnlKeysMousePressed
        int keyWidth = KeyboardRenderer.calcKeyWidth(pnlKeys, controller.getNumKeys()) ;
        int keyIndex = evt.getX() / keyWidth ;
        int baseKeyIndex = keyIndex % 7 ;
        
        // determine octave
        int numOctaves = controller.getNumKeys() / 7 ;
        int baseOctave = (8 - numOctaves) / 2 + 1 ;
        int octave = baseOctave + keyIndex / 7 ;

        // determine accidental
        Accidental accidental = Accidental.None ;
        int blackKeyHeight = KeyboardRenderer.calcBlackNoteHeight(pnlKeys, controller.getNumKeys());
        if (evt.getY() <= blackKeyHeight) {
            int blackNoteWidth = KeyboardRenderer.calcBlackNoteWidth(pnlKeys, controller.getNumKeys());
            int keyPos = evt.getX() % keyWidth ;
            boolean isFlat = keyPos < blackNoteWidth/2 ;
            boolean isSharp = keyPos > keyWidth - blackNoteWidth/2 ;
            if (isSharp && baseKeyIndex != 2 && baseKeyIndex != 6)
                accidental = Accidental.Sharp ;
            else if (isFlat && baseKeyIndex != 0 && baseKeyIndex != 3) {
                accidental = Accidental.Sharp;
                keyIndex-- ;
                baseKeyIndex = baseKeyIndex != 0 ? baseKeyIndex - 1 : 6 ;
            }
        }

        // determine key
        Key key = Key.C;
        switch (baseKeyIndex) {
            case 0: key = Key.C; break ;
            case 1: key = Key.D; break ;
            case 2: key = Key.E; break ;
            case 3: key = Key.F; break ;
            case 4: key = Key.G; break ;
            case 5: key = Key.A; break ;
            case 6: key = Key.B; break ;
        }

        this.renderKeyboard(keyIndex, accidental == Accidental.Sharp);
        this.playNote(key, accidental, octave);
    }//GEN-LAST:event_pnlKeysMousePressed

    private void pnlKeysMouseReleased(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_pnlKeysMouseReleased
        this.renderKeyboard(-1, false);
    }//GEN-LAST:event_pnlKeysMouseReleased

    private void btnStopPlayMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnStopPlayMousePressed
        controller.stopPlay() ;
    }//GEN-LAST:event_btnStopPlayMousePressed

    private void btnPauseMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnPauseMousePressed
        controller.pause();
    }//GEN-LAST:event_btnPauseMousePressed

    private void pnlSheetMusicMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_pnlSheetMusicMousePressed
        this.sheetMusicRenderer.setSongChordPosition(
                controller.getSong(),
                new Integer(ddlVoice.getSelectedItem().toString()),
                evt.getPoint());
        renderSheetMusic();
    }//GEN-LAST:event_pnlSheetMusicMousePressed

    private void btnStopRecordMousePressed(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnStopRecordMousePressed
        controller.stopRecord() ;
        renderSheetMusic() ;
    }//GEN-LAST:event_btnStopRecordMousePressed

    private void btnEnableSheetMusicMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_btnEnableSheetMusicMouseClicked
        if (btnEnableSheetMusic.isSelected()) {
            btnEnableSheetMusic.setText("Disable");
        }
        else
            btnEnableSheetMusic.setText("Enable");
    }//GEN-LAST:event_btnEnableSheetMusicMouseClicked

    private static<T extends Comparable<? super T>> ArrayList<T> sort(Collection<T> c) {
      ArrayList<T> list = new ArrayList<T>(c);
      java.util.Collections.sort(list);
      return list;
    }

    private <T> void selectValueInDropdown(javax.swing.JComboBox ddl, Map<String, T> textValueMap, T value) {
        for (String selectedText : textValueMap.keySet()) {
            if (textValueMap.get(selectedText) == value) {
                ddl.setSelectedItem(selectedText);
                break ;
            }
         }
    }

    private void renderSheetMusic(Graphics g) {
        pnlSheetMusic.removeAll();
        pnlSheetMusic.updateUI();
        sheetMusicRenderer.draw(g, pnlSheetMusic, controller.getSong());
        this.pnlSheetMusic.revalidate();
    }
    private void renderSheetMusic() {
        renderSheetMusic(pnlSheetMusic.getGraphics());
    }

    private void renderKeyboard(Graphics g, int keyPressedIndex, boolean blackKeyPressed) {
        keyboardRenderer.draw(g, pnlKeys, controller.getNumKeys(), keyPressedIndex, blackKeyPressed, btnChord.isSelected());
        pnlKeyboard.revalidate();
    }
    private void renderKeyboard(int keyPressedIndex, boolean blackKeyPressed) {
        renderKeyboard(pnlKeys.getGraphics(), keyPressedIndex, blackKeyPressed);
    }

    private void showErrorInDialog(String message) {
        JOptionPane.showMessageDialog(null, message, "Error", JOptionPane.ERROR_MESSAGE);
    }

    @Action
    public void print() {
        PrintUtilities.printComponent(this.pnlSheetMusic);
    }

    @Action
    public void save() {
        if (songFileChooser.showSaveDialog(jPanel2) == JFileChooser.APPROVE_OPTION) {
            File file = songFileChooser.getSelectedFile();
            if (songFileChooser.accept(file)) {
                if (!controller.save(file, pnlSheetMusic)) {
                    showErrorInDialog("Unexpected error saving song.");
                }
            }
            else {
                showErrorInDialog("Cannot save file in that format.");
            }
        }
    }

    @Action
    public void load() {
        if (songFileChooser.showOpenDialog(jPanel2) == JFileChooser.APPROVE_OPTION) {
            File file = songFileChooser.getSelectedFile();
            if (!songFileChooser.accept(file))
                showErrorInDialog("Cannot load file of that format.");
            if (controller.load(file)) {
                renderSheetMusic();

                // update song properties
                Song song = controller.getSong();
                this.txtTitle.setText(song.getTitle());
                this.selectValueInDropdown(ddlTempo, tempoMap, song.getTempo());
                this.selectValueInDropdown(ddlKeySignature, keySignatureMap, song.getKeySignature());
                trebleVoicesInListbox = song.getTrebleVoices();
                bassVoicesInListbox = song.getBassVoices();
                lbxTrebleVoices.setListData(trebleVoicesInListbox.toArray());
                lbxBassVoices.setListData(bassVoicesInListbox.toArray());

            } else {
                showErrorInDialog("Unexpected error loading song.");
            }
        }
    }

    @Action
    public void play() {
        controller.play() ;
    }

    @Action
    public void rewind() {
        controller.rewind(new Integer(ddlVoice.getSelectedItem().toString()));
        renderSheetMusic();
    }

    @Action
    public void forward() {
        controller.forward(new Integer(ddlVoice.getSelectedItem().toString()));
        renderSheetMusic();
    }

    @Action
    public void clear() {
        controller.clear() ;
        renderSheetMusic();
    }

    @Action
    public void record() {
        controller.record() ;
    }

    @Action
    public void saveAsImage() {
        if (imageFileChooser.showSaveDialog(jPanel2) == JFileChooser.APPROVE_OPTION) {
            File file = imageFileChooser.getSelectedFile();
            if (imageFileChooser.accept(file)) {
                if (!controller.save(file, pnlSheetMusic)) {
                    showErrorInDialog("Unexpected error saving song as image.");
                }
            }
            else {
                showErrorInDialog("Cannot save song as image in that file format.");
            }
        }
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton btnBassToTreble;
    private javax.swing.JToggleButton btnChord;
    private javax.swing.JButton btnClear;
    private javax.swing.JToggleButton btnEnableSheetMusic;
    private javax.swing.JButton btnForward;
    private javax.swing.JButton btnLoad;
    private javax.swing.JButton btnPause;
    private javax.swing.JButton btnPlay;
    private javax.swing.JButton btnPrint;
    private javax.swing.JButton btnRecord;
    private javax.swing.JButton btnRemove;
    private javax.swing.JButton btnRest;
    private javax.swing.JButton btnRewind;
    private javax.swing.JButton btnSave;
    private javax.swing.JButton btnSetSongProperties;
    private javax.swing.JButton btnStopPlay;
    private javax.swing.JButton btnStopRecord;
    private javax.swing.JButton btnTrebleToBass;
    private javax.swing.JComboBox ddlKeySignature;
    private javax.swing.JComboBox ddlTempo;
    private javax.swing.JComboBox ddlVoice;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabel5;
    private javax.swing.JLabel jLabel6;
    private javax.swing.JLabel jLabel7;
    private javax.swing.JLabel jLabel8;
    private javax.swing.JLabel jLabel9;
    private javax.swing.JMenuItem jMenuItem1;
    private javax.swing.JMenuItem jMenuItem2;
    private javax.swing.JMenuItem jMenuItem3;
    private javax.swing.JMenuItem jMenuItem4;
    private javax.swing.JMenuItem jMenuItem5;
    private javax.swing.JMenuItem jMenuItem6;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JLabel lblBeatsPerMinute;
    private javax.swing.JLabel lblError;
    private javax.swing.JList lbxBassVoices;
    private javax.swing.JList lbxTrebleVoices;
    private javax.swing.JMenuItem loadMenuItem;
    private javax.swing.JPanel mainPanel;
    private javax.swing.JMenuBar menuBar;
    private javax.swing.JPanel pnlKeyboard;
    private javax.swing.JPanel pnlKeys;
    private javax.swing.JPanel pnlSheetMusic;
    private javax.swing.JScrollPane pnlSheetMusicScrollPane;
    private javax.swing.JPanel pnlSongProperties;
    private javax.swing.JMenuItem printMenuItem;
    private javax.swing.JProgressBar progressBar;
    private javax.swing.JMenuItem saveMenuItem;
    private javax.swing.JSlider sliderBeat;
    private javax.swing.JMenu songMenu;
    private javax.swing.JLabel statusAnimationLabel;
    private javax.swing.JLabel statusMessageLabel;
    private javax.swing.JPanel statusPanel;
    private javax.swing.JTextField txtTitle;
    // End of variables declaration//GEN-END:variables

    private final Timer messageTimer;
    private final Timer busyIconTimer;
    private final Icon idleIcon;
    private final Icon[] busyIcons = new Icon[15];
    private int busyIconIndex = 0;

    private JDialog aboutBox;
}